/**
 * @author: zzj
 * @date: 2021/12/1
 */
const ejs = require("ejs");
const fs = require("fs");
const axios = require("axios");
//js关键字
const keyword = 'break、else、new、var、case、finally、return、void、catch、for、switch、while、continue、function、this、with、default、if、throw、delete、in、try、do、instranceof、typeof';
//js保留字
const reservedWord = 'abstract、enum、int、short、boolean、export、interface、static、byte、extends、long、super、char、final、native、synchronized、class、float、package、throws、const、goto、private 、transient、debugger、implements、protected 、volatile、double、import、public';
//注释模板
const commentTemp =
  `/**
 * <%= summary %>
 */`
//api function模板
const functionTemp = `
${commentTemp}
export function <%= name %>(<%= pathVariables||params %>) {
  return request({
    url: <%= url %>,
    method: '<%= method %>',
    <%_ if(!pathVariables){ _%>
    <%= params %>
    <%_ } _%>
  })
}
`;
//api decorator模板
const decoratorTemp = `
  /**
   * <%= summary %>
   */
  @<%= method %><%- url %>
  <%= name %>() {}
`

class ApiGenerator {
  //文档地址
  docUrl = process.argv[2] || '';
  //生成的api类型，0 function 1 class+decorator
  apiType = process.argv[3] || '0';
  //api生成路径
  apiPath = process.argv[4] || (process.cwd() + '/api');
  apiData = {};
  fileList = [];
  //用于方法重名时添加动作前缀
  actionMap = {
    get: ['get'],
    put: ['update', 'submit', 'edit'],
    post: ['add', 'save', 'insert'],
    delete: ['delete', 'remove']
  };
  //当前输入指针：0 文档地址，1 api类型，2 api生成路径
  stdinIndex = -1;

  /**
   * 首字母大写
   * @param name
   * @param upperCase
   * @returns {string}
   */
  toCapitalize(name, upperCase = true) {
    if (!name) return "";
    return (upperCase ? name[0].toUpperCase() : name[0].toLowerCase()) + name.substring(1);
  }

  /**
   * 下划线/横杠转驼峰
   * @param name
   * @param capitalize
   * @returns {string}
   */
  toCamelBak(name, capitalize) {
    if (!name) return "";
    let ary = name.split(/[-_]/g);
    return ary.map((v, i) => {
      if (i || capitalize) {
        return this.toCapitalize(v);
      } else {
        return v;
      }
    }).join("");
  }

  /**
   * api生成入口
   */
  start() {
    if (this.docUrl) {
      this.getApiDocs();
    } else {
      this.watchStdin();
    }
  }

  getApiDocs() {
    let url = this.docUrl.replace('/doc.html', '');
    axios.get(url + '/v2/api-docs').then(res => {
      let {tags = []} = res.data;
      if (!tags || !tags.length) {
        console.error('无效的文档地址');
        this.reset();
        return;
      }
      this.apiData = res.data;
      if (this.stdinIndex > -1) {
        this.stdinIndex++;
        this.printTips();
      } else {
        this.build();
      }
    }).catch(err => {
      console.error('获取api文档出错', err);
      this.reset();
    })
  }

  /**
   * 获取动作前缀
   * @param method
   * @param index
   * @returns {string}
   */
  getAction(method, index = 0) {
    //TODO 添加动作前缀策略优化
    let action = '';
    let actions = this.actionMap[method];
    if (actions) {
      action = actions[index % actions.length] || actions[0];
    }
    return action;
  }

  build() {
    this.setFileList();
    if (this.apiType === '0') {
      this.buildFunctionTemp();
    } else {
      this.buildDecoratorTemp();
    }
    this.writeFile();
  }

  watchStdin() {
    this.stdinIndex++;
    this.printTips();
    process.stdin.on("data", input => {
      let result = input.toString().trim().toLowerCase();
      switch (this.stdinIndex) {
        case 0:
          this.docUrl = result;
          this.getApiDocs();
          break;
        case 1:
          if (result) this.apiType = result;
          this.stdinIndex++;
          this.printTips();
          break;
        case 2:
          if (result) this.apiPath = result;
          this.build();
      }
    })
  }

  printTips() {
    switch (this.stdinIndex) {
      case 0:
        return console.log('请输入api文档地址：');
      case 1:
        return console.log('请选择生成的api类型：0、function 1、class+decorator (默认0)');
      case 2:
        return console.log('请输入api文件生成路径：(默认当前路径的api文件夹下)');
    }
  }

  setFnName(api, fnNames) {
    let {fnName, method} = api;
    //方法名在同一文件下重复或是关键字，添加前缀
    if (fnNames[fnName] > 1 || keyword.includes(fnName) || reservedWord.includes(fnName)) {
      api.fnName = method + this.toCapitalize(fnName);
      api.aliasFlag = true;
    }
  }

  buildFunctionTemp() {
    this.fileList.forEach(file => {
      let {apiList = [], fnNames = {}} = file;
      file.temp = 'import request from \'@/utils/request\';\n';
      file.temp += ejs.render(commentTemp, {
        summary: file.name,
      }) + '\n';
      apiList.forEach(api => {
        let {method, path} = api;
        this.setFnName(api, fnNames);
        //替换路径参数
        let pathVariables = [];
        path = path.replace(/({\w+})/ig, function (match) {
          pathVariables.push(match.replace(/[{}]/g, ''));
          return '$' + match;
        });
        let temp = ejs.render(functionTemp, {
          name: api.fnName,
          summary: api.summary,
          url: '`' + path + '`',
          method: method,
          params: method === 'get' ? 'params' : 'data',
          pathVariables: pathVariables.join(',')
        });
        file.temp += temp;
      })
    })
  }

  buildDecoratorTemp() {
    this.fileList.forEach(file => {
      file.basePath = file.basePathList.join('/');
      let {apiList = [], fnNames = {}, fileName, basePath} = file;
      file.temp = 'import {Api, Get, Post, Put, Delete} from \'@/decorator/request\';\n\n';
      file.temp += ejs.render(commentTemp, {
        summary: file.name,
      }) + '\n';
      file.temp += `@Api('${basePath}')\n`;
      file.temp += `class ${this.toCapitalize(fileName)} {\n`;
      apiList.forEach(api => {
        let {method, path} = api;
        path = path.replace(basePath, '');
        this.setFnName(api, fnNames);
        let url = '';
        if (/{\w+}/ig.test(path) || api.aliasFlag) {
          url = `('${path}')`;
        } else if (!path) {
          url = `()`;
        }
        let temp = ejs.render(decoratorTemp, {
          name: api.fnName,
          summary: api.summary,
          url: url,
          method: this.toCapitalize(method),
        });
        file.temp += temp;
      })
      file.temp += `}\n\n`;
      file.temp += `export const ${fileName} = new ${this.toCapitalize(fileName)}();`;
    })
  }

  setFileList() {
    let {tags = [], paths = {}} = this.apiData;
    Object.keys(paths).forEach(path => {
      let item = paths[path];
      let methods = Object.keys(item);
      methods.forEach(method => {
        let api = item[method];
        api.method = method;
        api.path = path;
        let tag = tags.find(v => v.name === api.tags[0]);
        if (tag) {
          //设置文件名
          if (!tag.fileName) {
            let fileName = tag.description.replace(/ /g, '').replace('Controller', '');
            tag.fileName = this.toCapitalize(fileName, false);
          }
          // api分类
          if (!tag.apiList) {
            tag.apiList = [];
          }
          tag.apiList.push(api);
          // 统计方法名，用于后续去重
          if (!tag.fnNames) {
            tag.fnNames = [];
          }
          let pathAry = path.split('/').filter(v => v).reverse();
          let fnName = pathAry.find(v => !/{\w+}/ig.test(v)) || pathAry[0];
          fnName = this.toCamelBak(fnName);
          api.fnName = fnName;
          // let fnNameUseMethod = fnName + '_' + method;
          this.setFnNamesNum(tag.fnNames, fnName);
          // this.setFnNamesNum(tag.fnNames, fnNameUseMethod);
          // api.nameIndex = tag.fnNames[fnNameUseMethod] - 1;
          this.setBasePath(tag, path);
        }
      })
    });
    this.fileList = tags;
  }

  setFnNamesNum(fnNames = {}, fnName) {
    if (fnNames[fnName]) {
      fnNames[fnName]++;
    } else {
      fnNames[fnName] = 1;
    }
  }

  setBasePath(tag, path) {
    let list = path.split('/');
    if (!tag.basePathList) {
      tag.basePathList = list;
    } else {
      tag.basePathList = tag.basePathList.filter((v, i) => v === list[i]);
    }
  }

  writeFile() {
    let filePathList = this.apiPath.split(/(\/+|\\+)/g).filter(v => v && !/(\/+|\\+)/.test(v));
    let filePath = filePathList.shift();
    this.checkDir(filePath, filePathList);
    let index = 0;
    this.fileList.forEach(file => {
      let {fileName, temp} = file;
      let realPath = this.apiPath + `/${fileName}.js`;
      fs.writeFile(realPath, temp, (err, res) => {
        index++;
        if (err) {
          console.log(err);
        } else {
          console.log("文件生成成功，位于" + realPath);
        }
        if (index === this.fileList.length) {
          this.exit();
        }
      })
    })
  }

  checkDir(filePath, filePathList = []) {
    if (!fs.existsSync(filePath)) {
      fs.mkdirSync(filePath);
    }
    if (filePathList.length) {
      let path = filePathList.shift();
      filePath += "/" + path;
      this.checkDir(filePath, filePathList);
    }
    this.clearDir(this.apiPath);
  }

  clearDir(dirPath) {
    let fileList = fs.readdirSync(dirPath);
    fileList.forEach(file => {
      let path = dirPath + '/' + file;
      let stats = fs.statSync(path);
      if (stats.isDirectory()) {
        this.clearDir(path)
      } else {
        fs.unlinkSync(path);
      }
    })
  }

  reset() {
    if (this.stdinIndex > -1) {
      this.stdinIndex = 0;
      this.printTips();
    }
  }

  exit() {
    process.exit(0);
  }
}

// new ApiGenerator().start();
exports.ApiGenerator = ApiGenerator;
